﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Globalization;
using System.Reflection.Emit;

namespace CommonUtils
{
	public class Reflection
	{
		/// <summary>
		/// 
		/// </summary>
		/// <param name="args"></param>
		/// <returns></returns>
		public static Type[] GetTypes(object[] args)
		{
			if (args == null || args.Length == 0)
				return Type.EmptyTypes;

			Type[] paramsType = new Type[args.Length];
			for (int i = 0; i < args.Length; ++i)
			{
				object arg = args[i];
				paramsType[i] = (arg != null) ? args[i].GetType() : typeof(object);
			}
			return paramsType;
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="type"></param>
		/// <param name="name"></param>
		/// <param name="types"></param>
		/// <returns></returns>
		public static MethodInfo GetMethodIgnoreCase(Type type, string name, Type[] types)
		{
			Assert.ArgumentNotNull(type, "type");
			Assert.ArgumentHasText(name, "name");
			Assert.ArgumentNotNull(types, "types");

			MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public);
			foreach (MethodInfo mi in methods)
			{
				if (mi.Name.ToUpper().Equals(name.ToUpper()))
				{
					ParameterInfo[] pinfos = mi.GetParameters();
					if (types.Length == pinfos.Length)
					{
						bool bFound = true;
						for (int i = 0; i < types.Length; i++)
						{
							if (!pinfos[i].ParameterType.IsAssignableFrom(types[i]))
							{
								bFound = false;
								break;
							}
						}

						if (bFound)
							return mi;
					}
				}
			}
			return null;
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="type"></param>
		/// <param name="name"></param>
		/// <param name="parameters"></param>
		/// <returns></returns>
		public static MethodInfo GetMethodByParameters(Type type, string name, object[] parameters)
		{
			Assert.ArgumentNotNull(type, "type");
			Assert.ArgumentHasText(name, "name");
			Assert.ArgumentNotNull(parameters, "parameters");

			Type[] types = Reflection.GetTypes(parameters);
			MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public);
			foreach (MethodInfo mi in methods)
			{
				if (mi.Name.ToUpper().Equals(name.ToUpper()))
				{
					ParameterInfo[] pinfos = mi.GetParameters();
					if (parameters.Length == pinfos.Length)
					{
						bool bFound = true;
						for (int i = 0; i < types.Length; i++)
						{
							try
							{
								if (!pinfos[i].ParameterType.IsAssignableFrom(types[i]) && Convert.ChangeType(parameters[i], pinfos[i].ParameterType) == null)
								{
									bFound = false;
									break;
								}
							}
							catch (Exception e) { }
						}

						if (bFound)
							return mi;
					}
				}
			}
			return null;
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="type"></param>
		/// <param name="name"></param>
		/// <returns></returns>
		public static MethodInfo GetMethodByName(Type type, string name)
		{
			Assert.ArgumentNotNull(type, "type");
			Assert.ArgumentHasText(name, "name");

			MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public);
			foreach (MethodInfo mi in methods)
			{
				if (mi.Name.Equals(name))
				{
					return mi;
				}
			}

			return null;
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="type"></param>
		/// <returns></returns>
		public static object GetDefaultValue(Type type)
		{
			if (!type.IsValueType)
			{
				return null;
			}
			if (type == typeof(Boolean))
			{
				return false;
			}
			if (type == typeof(DateTime))
			{
				return DateTime.MinValue;
			}
			if (type == typeof(Char))
			{
				return Char.MinValue;
			}
			if (type.IsEnum)
			{
				Array values = Enum.GetValues(type);
				if (values == null || values.Length == 0)
					throw new ArgumentException("Bad 'enum' Type : cannot get default value because 'enum' has no values.");
			
				return values.GetValue(0);
			}
			return 0;
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="types"></param>
		/// <returns></returns>
		public static object[] GetDefaultValues(Type[] types)
		{
			object[] defaults = new object[types.Length];
			for (int i = 0; i < types.Length; ++i)
			{
				defaults[i] = GetDefaultValue(types[i]);
			}
			return defaults;
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="args"></param>
		/// <returns></returns>
		public static Type[] GetParameterTypes(ParameterInfo[] args)
		{
			Assert.ArgumentNotNull(args, "args");

			Type[] types = new Type[args.Length];
			for (int i = 0; i < args.Length; i++)
				types[i] = args[i].ParameterType;

			return types;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="propertyName"></param>
		/// <param name="value"></param>
		public static void Set(object o, string propertyName, object value)
		{
			Assert.ArgumentNotNull(o, "o");
			Assert.ArgumentNotNull(propertyName, "propertyName");

			PropertyInfo p = Reflection.GetPropertyInfo(o, propertyName);
			Assert.ArgumentNotNull(p, "propertyName", "Invalid property name");
			Assert.IsTrue(p.CanWrite, "Property is read-only and cannot be written to");

			object _converted_val = null;
			try
			{
				if (value != null && !p.PropertyType.IsAssignableFrom(value.GetType()))
				{
					if (value is string && p.PropertyType == typeof(Guid))
						_converted_val = new Guid((string)value);
					else
						_converted_val = Convert.ChangeType(value, p.PropertyType, CultureInfo.CurrentCulture);
				}
				else
					_converted_val = value;

				p.SetValue(o, _converted_val, null);
			}
			catch (Exception fe)
			{
				throw new Exception(string.Format("Cannot put value {0} in property {1} on object {2}", value, propertyName, o), fe);
			}
		}
		/// <summary>
		/// 
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="propertyName"></param>
		/// <returns></returns>
		public static T Get<T>(object o, string propertyName)
		{
			Assert.ArgumentNotNull(o, "o");
			Assert.ArgumentNotNull(propertyName, "propertyName");

			string[] propertyParts = propertyName.Split('.');
			PropertyInfo p = null;
			object returnValue = o;
			for (int i = 0; i < propertyParts.Length; i++)
			{
				p = Reflection.GetPropertyInfo(returnValue, propertyName);
				Assert.ArgumentNotNull(p, "propertyName", "Invalid property name");
				returnValue = (T)p.GetValue(returnValue, null);
			}

			return (T)returnValue;
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="propertyName"></param>
		/// <returns></returns>
		public static bool HasProperty(object o, string propertyName)
		{
			Assert.ArgumentNotNull(o, "o");
			Assert.ArgumentNotNull(propertyName, "propertyName");

			return Reflection.GetPropertyInfo(o, propertyName) != null;
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="propertyName"></param>
		/// <returns></returns>
		public static PropertyInfo GetPropertyInfo(object o, string propertyName)
		{
			Assert.ArgumentNotNull(o, "o");
			Assert.ArgumentNotNull(propertyName, "propertyName");

			return o.GetType().GetProperty(propertyName);
		}
		/// <summary>
		/// Returns first property of type @propertyType
		/// </summary>
		/// <param name="o"></param>
		/// <param name="propertyType"></param>
		/// <returns></returns>
		public static PropertyInfo GetPropertyInfo(object o, Type propertyType)
		{
			Assert.ArgumentNotNull(o, "o");
			Assert.ArgumentNotNull(propertyType, "propertyType");

			PropertyInfo[] properties = GetPropertyInfo(o);
			foreach (PropertyInfo info in properties)
			{
				if (info.PropertyType.IsAssignableFrom(propertyType))
					return info;
			}
			return null;
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="propertyName"></param>
		/// <returns></returns>
		public static Type GetPropertyType(object o, string propertyName)
		{
			Assert.ArgumentNotNull(o, "o");
			Assert.ArgumentNotNull(o, "propertyName");

			PropertyInfo info = o.GetType().GetProperty(propertyName);
			if (info != null)
				return info.PropertyType;

			return null;
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="o"></param>
		/// <returns></returns>
		public static PropertyInfo[] GetPropertyInfo(object o)
		{
			Assert.ArgumentNotNull(o, "o");

			return o.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
		}
		/// <summary>
		/// 
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="o"></param>
		/// <param name="propertyName"></param>
		/// <param name="inherit"></param>
		/// <returns></returns>
		public static List<T> GetCustomAttributes<T>(object o, string propertyName, bool inherit)
		{
			PropertyInfo pi = Reflection.GetPropertyInfo(o, propertyName);
			if (pi != null)
			{
				object[] attributes = pi.GetCustomAttributes(typeof(T), true);
				if (attributes != null && attributes.Length > 0)
				{
					List<T> result = new List<T>(attributes.Length);
					foreach (object attr in attributes)
						result.Add((T)attr);

					return result;
				}
			}
			return null;
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="to"></param>
		public static void CopyTo(object from, object to)
		{
			Assert.ArgumentNotNull(from, "from");
			Assert.ArgumentNotNull(to, "to");

			Type fromType = from.GetType();
			Type toType = to.GetType();
			PropertyInfo[] pi = fromType.GetProperties();

			for (int i = 0; i < pi.Length; i++)
			{
				PropertyInfo piTo = toType.GetProperty(pi[i].Name);
				if (piTo != null && piTo.CanWrite)
				{
					piTo.SetValue(to, pi[i].GetValue(from, null), null);
				}
			}
		}
		/*
		public static T DeepCopy<T>(this T oSource)
		{
			T oClone;
			DataContractSerializer dcs = new DataContractSerializer(typeof(T));
			using (MemoryStream ms = new MemoryStream())
			{
				dcs.WriteObject(ms, oSource);
				ms.Position = 0;
				oClone = (T)dcs.ReadObject(ms);
			}
			return oClone;
		}
		*/
		/// <summary>
		/// Creates a <see cref="System.Reflection.Emit.CustomAttributeBuilder"/>.
		/// </summary>
		/// <remarks>
		/// <p>
		/// Note that if a non-<see langword="null"/> <paramref name="sourceAttribute"/>
		/// is supplied, any read write properties exposed by the <paramref name="sourceAttribute"/>
		/// will be used to overwrite values that may have been passed in via the
		/// <paramref name="ctorArgs"/>. That is, the <paramref name="ctorArgs"/> will be used
		/// to initialize the custom attribute, and then any read-write properties on the
		/// <paramref name="sourceAttribute"/> will be plugged in.
		/// </p>
		/// </remarks>
		/// <param name="type">
		/// The desired <see cref="System.Attribute"/> <see cref="System.Type"/>.
		/// </param>
		/// <param name="ctorArgs">
		/// Any constructor arguments for the attribute (may be <see langword="null"/>
		/// in the case of no arguments).
		/// </param>
		/// <param name="sourceAttribute">
		/// Source attribute to copy properties from (may be <see langword="null"/>).
		/// </param>
		/// <returns>A custom attribute builder.</returns>
		/// <exception cref="System.ArgumentNullException">
		/// If the <paramref name="type"/> parameter is <see langword="null"/>.
		/// </exception>
		/// <exception cref="System.ArgumentNullException">
		/// If the <paramref name="type"/> parameter is not a <see cref="System.Type"/>
		/// that derives from the <see cref="System.Attribute"/> class.
		/// </exception>
		/// <seealso cref="System.Reflection.Emit.CustomAttributeBuilder"/>
		public static CustomAttributeBuilder CreateCustomAttribute(Type type, object[] ctorArgs, Attribute sourceAttribute)
		{
			Assert.ArgumentNotNull(type, "type");
			if (!typeof(Attribute).IsAssignableFrom(type))
				throw new ArgumentException(string.Format("[{0}] does not derive from the [System.Attribute] class.", type.FullName));

			ConstructorInfo ci = type.GetConstructor(GetTypes(ctorArgs));
			if (ci == null && ctorArgs.Length == 0)
			{
				ci = type.GetConstructors()[0];
				ctorArgs = GetDefaultValues(GetParameterTypes(ci.GetParameters()));
			}

			if (sourceAttribute != null)
			{
				object defaultAttribute = null;
				try
				{
					defaultAttribute = ci.Invoke(ctorArgs);
				}
				catch
				{
				}

				IList<PropertyInfo> getSetProps = new List<PropertyInfo>();
				IList<object> getSetValues = new List<object>();
				IList<PropertyInfo> readOnlyProps = new List<PropertyInfo>();
				IList<object> readOnlyValues = new List<object>();

				foreach (PropertyInfo pi in type.GetProperties(BindingFlags.Instance | BindingFlags.Public))
				{
					if (pi.DeclaringType == typeof(Attribute))
						continue;

					if (pi.CanRead)
					{
						if (Reflection.CanWriteToProperty(pi))
						{
							object propValue = pi.GetValue(sourceAttribute, null);
							if (defaultAttribute != null)
							{
								object defaultValue = pi.GetValue(defaultAttribute, null);
								if ((propValue == null && defaultValue == null) ||
									(propValue != null && propValue.Equals(defaultValue)))
									continue;
							}
							getSetProps.Add(pi);
							getSetValues.Add(propValue);
						}
						else
						{
							readOnlyProps.Add(pi);
							readOnlyValues.Add(pi.GetValue(sourceAttribute, null));
						}
					}
				}

				if (readOnlyProps.Count == 1)
				{
					PropertyInfo pi = readOnlyProps[0];
					ConstructorInfo ciTemp = type.GetConstructor(new Type[] { pi.PropertyType });
					if (ciTemp != null)
					{
						ci = ciTemp;
						ctorArgs = new object[] { readOnlyValues[0] };
					}
					else
					{
						ciTemp = type.GetConstructor(new Type[] { readOnlyValues[0].GetType() });
						if (ciTemp != null)
						{
							ci = ciTemp;
							ctorArgs = new object[] { readOnlyValues[0] };
						}
					}
				}

				PropertyInfo[] propertyInfos = new PropertyInfo[getSetProps.Count];
				getSetProps.CopyTo(propertyInfos, 0);

				object[] propertyValues = new object[getSetValues.Count];
				getSetValues.CopyTo(propertyValues, 0);

				return new CustomAttributeBuilder(ci, ctorArgs, propertyInfos, propertyValues);
			}
			else
			{
				return new CustomAttributeBuilder(ci, ctorArgs);
			}
		}
		/// <summary>
		/// Work around for the PropertyInfo.CanWrite method. CanWrite also returns true even if property has a private setter for some reason.
		/// </summary>
		/// <param name="pi"></param>
		/// <returns></returns>
		private static bool CanWriteToProperty(PropertyInfo pi)
		{
			MethodInfo[] accessors = pi.GetAccessors(true);
			for (int i = 0; i < accessors.Length; i++)
			{
				if (accessors[i].Name.StartsWith("set_") && !accessors[i].IsPrivate)
				{
					return true;
				}
			}
			return false;
		}
		/// <summary>
		/// Creates a <see cref="System.Reflection.Emit.CustomAttributeBuilder"/>.
		/// </summary>
		/// <param name="type">
		/// The desired <see cref="System.Attribute"/> <see cref="System.Type"/>.
		/// </param>
		/// <param name="sourceAttribute">
		/// Source attribute to copy properties from (may be <see langword="null"/>).
		/// </param>
		/// <returns>A custom attribute builder.</returns>
		public static CustomAttributeBuilder CreateCustomAttribute(Type type, Attribute sourceAttribute)
		{
			return CreateCustomAttribute(type, new object[] { }, sourceAttribute);
		}

		/// <summary>
		/// Creates a <see cref="System.Reflection.Emit.CustomAttributeBuilder"/>.
		/// </summary>
		/// <param name="sourceAttribute">
		/// The source attribute to copy properties from.
		/// </param>
		/// <returns>A custom attribute builder.</returns>
		/// <exception cref="System.NullReferenceException">
		/// If the supplied <paramref name="sourceAttribute"/> is
		/// <see langword="null"/>.
		/// </exception>
		public static CustomAttributeBuilder CreateCustomAttribute(Attribute sourceAttribute)
		{
			return CreateCustomAttribute(sourceAttribute.GetType(), sourceAttribute);
		}

		/// <summary>
		/// Creates a <see cref="System.Reflection.Emit.CustomAttributeBuilder"/>.
		/// </summary>
		/// <param name="type">
		/// The desired <see cref="System.Attribute"/> <see cref="System.Type"/>.
		/// </param>
		/// <returns>A custom attribute builder.</returns>
		public static CustomAttributeBuilder CreateCustomAttribute(Type type)
		{
			return CreateCustomAttribute(type, new object[] { }, null);
		}

		/// <summary>
		/// Creates a <see cref="System.Reflection.Emit.CustomAttributeBuilder"/>.
		/// </summary>
		/// <param name="type">
		/// The desired <see cref="System.Attribute"/> <see cref="System.Type"/>.
		/// </param>
		/// <param name="ctorArgs">
		/// Any constructor arguments for the attribute (may be <see langword="null"/>
		/// in the case of no arguments).
		/// </param>
		/// <returns>A custom attribute builder.</returns>
		public static CustomAttributeBuilder CreateCustomAttribute(Type type, params object[] ctorArgs)
		{
			return CreateCustomAttribute(type, ctorArgs, null);
		}
		/// <summary>
		/// 
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <returns></returns>
		public static List<Type> GetTypesFromAssembly<T>()
		{
			List<Type> foundTypes = new List<Type>();
			var type = typeof(T);

			foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
			{
				foreach (var t in asm.GetTypes())
				{
					if (type.IsAssignableFrom(t))
					{
						foundTypes.Add(t);
					}
				}
			}

			return foundTypes;
		}
	}
}
